﻿using GACore.Structures;
using System;

namespace GACore
{
	public abstract class Vector : IComparable
	{
		protected double fitness;
		public double Fitness
		{
			get { return fitness; }
			set { fitness = value; }
		}

		protected FitnessProperties fitnessProperties;
		protected CrossoverProperties crossoverProperties;

		public Vector(FitnessProperties fitnessProperties, CrossoverProperties crossoverProperties)
		{
			this.fitnessProperties = fitnessProperties;
			this.crossoverProperties = crossoverProperties;
		}

		public Vector[] DoCrossover(Vector alliedVector)
		{
			switch (crossoverProperties.CrossoverSchema)
			{
				case CrossoverSchema.Crossover1Point:
					return DoCrossover1Point(alliedVector);
				case CrossoverSchema.Crossover2Point:
					return DoCrossover2Point(alliedVector);
				case CrossoverSchema.CrossoverUniform:
					return DoCrossoverUniform(alliedVector);
				default:
					throw new ArgumentException("Bitte geben Sie ein gültiges Rekombinationsschema an.");
			}
		}

		protected abstract Vector[] DoCrossover1Point(Vector alliedVector);
		protected abstract Vector[] DoCrossover2Point(Vector alliedVector);
		protected abstract Vector[] DoCrossoverUniform(Vector alliedVector);

		public abstract void MutateVector(MutationProperties mutationProperties);
		public abstract void MutateElement(int elementIndex, MutationProperties mutationProperties);

		public int CompareTo(object obj)
		{
			int ret = 0;
			Vector tmp = (Vector)obj;
			if (this.fitness > tmp.fitness)
			{
				ret = 1;
			}
			else if (this.fitness < tmp.fitness)
			{
				ret = -1;
			}
			if (!fitnessProperties.MinimizeFitnessValue)
			{
				ret *= -1;
			}
			return ret;
		}

		public abstract int Length { get; }

		public abstract double ComputeFitnessSum();
		public abstract double ComputeFitnessCount(object objectToCount);
		public abstract double ComputeFitnessEuklidDistance(Vector compareVector);
		public abstract double ComputeFitnessHammingDistance(Vector compareVector);

	}

	public class Vector<T> : Vector
	{
		private T[] elements;
		public T[] Elements
		{
			get { return elements; }
			set { elements = value; }
		}

		public T this[int index]
		{
			get { return elements[index]; }
			set { elements[index] = value; }
		}

		public override int Length
		{
			get { return elements.Length; }
		}

		private Random random = new Random();

		public Vector(int elementsLength, FitnessProperties fitnessProperties, CrossoverProperties crossoverProperties)
			: base(fitnessProperties, crossoverProperties)
		{
			this.elements = new T[elementsLength];
		}

		private Vector(Vector<T> vector)
			: base(vector.fitnessProperties, vector.crossoverProperties)
		{
			this.elements = new T[vector.elements.Length];
			Array.Copy(vector.elements, elements, elements.Length);
		}

		public override void MutateVector(MutationProperties mutationProperties)
		{
			int count = (int)(mutationProperties.MutationRate * elements.Length);
			int randomIndex = random.Next(elements.Length);

			for (int i = 0; i < count; i++)
			{
				MutateElement((i + randomIndex) % elements.Length, mutationProperties);
			}
		}

		public override void MutateElement(int elementIndex, MutationProperties mutationProperties)
		{
			object mutationWidth = mutationProperties.MutationWidth;

			if (mutationWidth is double)
			{
				double value = (double)(object)elements[elementIndex];
				// Mutationsweite addieren oder subtrahieren?
				if (random.NextDouble() > 0.5)
				{
					value += (double)mutationWidth;
					// Wenn der Wert der Vektorelemente innerhalb bestimmter Grenzen liegen muss
					double difference = value - (double)mutationProperties.MaxValue;
					value = difference > 0 ? (double)mutationProperties.MinValue + difference : value;
				}
				else
				{
					value -= (double)mutationWidth;
					// Wenn der Wert der Vektorelemente innerhalb bestimmter Grenzen liegen muss
					double difference = value - (double)mutationProperties.MinValue;
					value = difference < 0 ? (double)mutationProperties.MaxValue + difference : value;
				}

				elements[elementIndex] = (T)(object)value;
			}
			else if (mutationWidth is int)
			{
				int value = (int)(object)elements[elementIndex];
				// Mutationsweite addieren oder subtrahieren?
				if (random.NextDouble() > 0.5)
				{
					value += (int)mutationWidth;
					// Wenn der Wert der Vektorelemente innerhalb bestimmter Grenzen liegen muss
					int difference = value - (int)mutationProperties.MaxValue;
					value = difference > 0 ? (int)mutationProperties.MinValue + difference : value;
				}
				else
				{
					value -= (int)mutationWidth;
					// Wenn der Wert der Vektorelemente innerhalb bestimmter Grenzen liegen muss
					int difference = value - (int)mutationProperties.MinValue;
					value = difference < 0 ? (int)mutationProperties.MaxValue + difference : value;
				}
				elements[elementIndex] = (T)(object)value;
			}
			else if (mutationWidth is bool)
			{
				if ((bool)mutationWidth)
				{
					bool value = (bool)(object)elements[elementIndex];
					value = !value;
					elements[elementIndex] = (T)(object)value;
				}
			}
			else
			{
				throw new NotImplementedException("Dieser Vektortyp ist nicht implementiert.");
			}
		}

		protected override Vector[] DoCrossover1Point(Vector alliedVector)
		{
			Vector[] ret = new Vector[2];
			int crossCutPoint = crossoverProperties.CrossoverPoints[0];
			Vector<T> tmpVector = (Vector<T>)alliedVector;

			ret[0] = new Vector<T>(this);
			Vector<T> retVector = (Vector<T>)ret[0];
			Array.Copy(tmpVector.elements, crossCutPoint, retVector.elements, crossCutPoint, retVector.elements.Length - crossCutPoint);

			ret[1] = new Vector<T>(this);
			retVector = (Vector<T>)ret[1];
			Array.Copy(tmpVector.elements, retVector.elements, crossCutPoint);

			return ret;
		}

		protected override Vector[] DoCrossover2Point(Vector alliedVector)
		{
			int start = this.crossoverProperties.CrossoverPoints[0];
			int length = crossoverProperties.CrossoverPoints[1] - crossoverProperties.CrossoverPoints[0];

			Vector<T> vector1 = new Vector<T>(this);
			Array.Copy(((Vector<T>)alliedVector).elements, start, vector1.elements, start, length);

			Vector<T> vector2 = new Vector<T>((Vector<T>)alliedVector);
			Array.Copy(this.elements, start, vector2.elements, start, length);

			return new Vector[] { vector1, vector2 };
		}

		protected override Vector[] DoCrossoverUniform(Vector alliedVector)
		{
			double fragmentRatio = 0.2;
			int fragmentLength = (int)(elements.Length * fragmentRatio);

			Vector[] ret = new Vector[2];
			Vector<T> tmpVector = (Vector<T>)alliedVector;

			int index1 = random.Next(elements.Length - 1);
			int index2 = random.Next(elements.Length - 1);

			Vector<T> ret0Vector = new Vector<T>(this);
			ret[0] = ret0Vector;

			Vector<T> ret1Vector = new Vector<T>(tmpVector);
			ret[1] = ret1Vector;

			for (int i = 0; i < fragmentLength; i++)
			{

				ret0Vector.elements[(index1 + i) % elements.Length] = tmpVector.elements[(index2 + i) % elements.Length];
				ret1Vector.elements[(index2 + i) % elements.Length] = this.elements[(index1 + i) % elements.Length];
			}

			return ret;
		}

		public override double ComputeFitnessSum()
		{
			double sum = 0;
			for (int i = 0; i < elements.Length; i++)
			{
				sum += GetDoubleFromT(elements[i]);
			}
			return sum;
		}

		private double GetDoubleFromT(T value)
		{
			if (value is int)
			{
				return (int)(object)value;
			}
			else if (value is double)
			{
				return (double)(object)value;
			}
			else if (value is bool)
			{
				bool ret = (bool)(object)value;
				return ret ? 1 : 0;
			}
			throw new NotImplementedException("Der generische Typ ist nicht implementiert.");
		}

		public override double ComputeFitnessCount(object objectToCount)
		{
			int count = 0;
			for (int i = 0; i < elements.Length; i++)
			{
				if (objectToCount.Equals((object)elements[i]))
				{
					count++;
				}
			}
			return count;
		}

		public override double ComputeFitnessEuklidDistance(Vector compareVector)
		{
			Vector<T> tmpVector = (Vector<T>)compareVector;
			double sum = 0;
			for (int i = 0; i < elements.Length; i++)
			{
				double difference = GetDoubleFromT(elements[i]) - GetDoubleFromT(tmpVector.elements[i]);
				sum += difference * difference;
			}
			return Math.Sqrt(sum);
		}

		public override double ComputeFitnessHammingDistance(Vector compareVector)
		{
			Vector<T> tmpVector = (Vector<T>)compareVector;
			double sum = 0;
			for (int i = 0; i < elements.Length; i++)
			{
				if (!((object)elements[i]).Equals((object)tmpVector.elements[i]))
				{
					sum++;
				}
			}
			return sum;
		}
		public override string ToString()
		{
			string ret = "(";
			foreach (var item in elements)
			{
				ret += item.ToString() + " ";
			}
			return ret.Substring(0, ret.Length - 1) + ")";
		}

	}
}
